//
//  PlugSuitPref.m
//  PlugSuit
//
//  Created by ∞ on 12/12/07.
//  Copyright (c) 2007 Emanuele Vulcano (infinite-labs.net). All rights reserved.
//

#import "PlugSuitPref.h"

#import "PlSInputManagerManager.h"
#import "PlSSIMBLManager.h"
#import "PlSSIMBLPlugIn.h"
#import "PlSCFPreferences.h"
#import "NSAlert+L0Alert.h"
#import "PlSUpdateUI.h"
#import "PlSTableView.h"

#define kPlSAgentIdentifier @"net.infinite-labs.PlugSuit.Agent"
#define kPlSPlugSuitErrorDomain @"kPlSPlugSuitErrorDomain"

#import "L0PlugSuitWire.h" // to migrate from 1.0 to 1.5

enum {
	kPlSPlugSuitErrorLaunchctl,
};

#define PlSLocalizedString(x, y) ([[self bundle] localizedStringForKey:(x) value:(x) table:nil])

#import <Carbon/Carbon.h>

@interface PlugSuitPref ()

- (void) _checkForEnabled;

@end

@implementation PlugSuitPref

- (BOOL) isEnabled {
	BOOL result;
	@try {
		id x = [NSConnection rootProxyForConnectionWithRegisteredName:kPlSDistributedObjectName host:nil];
		result = (x != nil);
	} @catch (NSException* ex) { result = NO; }
	
	return result; // shuts up the compiler, who is unable to see the
				   // fact that that all code paths above would be covered
				   // if I just return'd from both the try and the catch.
}

- (void) awakeFromNib {
	[_aboutTextField setStringValue:
	  [NSString stringWithFormat:[_aboutTextField stringValue], 
	   [[self bundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]]
	 ];
	
	_plugInsView.dragActionSelector = @selector(installPlugInAtPath:);
	_appsView.dragActionSelector = @selector(excludeApplicationAtPath:);
}

- (NSError*) launchctlError {
	return [NSError errorWithDomain:kPlSPlugSuitErrorDomain code:kPlSPlugSuitErrorLaunchctl userInfo:
			[NSDictionary dictionaryWithObjectsAndKeys:
			 PlSLocalizedString(@"Mac OS X returned an error when trying to modify PlugSuit's status.", @"Launchctl error description"), NSLocalizedDescriptionKey,
			 PlSLocalizedString(@"The system refused to start or stop PlugSuit as a system service for some reason, but didn't tell why. You could try again, or contact support at plugsuit@infinite-labs.net.",  @"Launchctl error reason"), NSLocalizedFailureReasonErrorKey,
			 nil]
			];
}

- (NSString*) pathToAgent {
	return [[self bundle] pathForResource:@"PlugSuit Agent" ofType:@"app"];
}

- (id) defaultLaunchdPropertyList {
	static NSDictionary* dict = nil; if (!dict) {
		dict = [[NSDictionary alloc] initWithObjectsAndKeys:
				kPlSAgentIdentifier, @"Label",
				[[self pathToAgent] stringByAppendingPathComponent:@"Contents/MacOS/PlugSuit Agent"], @"Program",
				[NSNumber numberWithBool:YES], @"RunAtLoad",
				@"Aqua", @"LimitLoadToSessionType",
				[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:@"SuccessfulExit"], @"KeepAlive",
				[NSNumber numberWithBool:NO], @"Disabled",
				nil];
	}
	
	return dict;
}

- (void) updateEnabledUI {
	BOOL doneWaiting = NO;
	if (_waitingForStartup) {
		if (![self isEnabled])
			[_enabledField setStringValue:PlSLocalizedString(@"Waiting\nfor PlugSuit...", @"Left-side label: working to enable PlugSuit")];
		else
			doneWaiting = YES;
	} else
		doneWaiting = YES;
	
	if (doneWaiting) {
		_waitingForStartup = NO;
		_timesCheckedForStartup = 0;
		[_spinner stopAnimation:self];
		[_enabledButton setEnabled:YES];
		
		NSString* message;
		NSString* buttonLabel;
		NSString* imageSuffix;
		if ([self isEnabled]) {
			message = PlSLocalizedString(@"PlugSuit\nis enabled.", @"Left-side label when PlugSuit is on.");
			buttonLabel = PlSLocalizedString(@"Disable It", @"Left-side button when PlugSuit is on.");
			imageSuffix = @"_PlusOn";
		} else {
			message = PlSLocalizedString(@"PlugSuit\nis disabled.", @"Left-side label when PlugSuit is off.");
			buttonLabel = PlSLocalizedString(@"Enable It", @"Left-side button when PlugSuit is off.");
			imageSuffix = @"_PlusOff";
		}
		
		[_enabledField setStringValue:message];
		[_enabledButton setTitle:buttonLabel];
		[_plusImageView setImage:[NSImage imageNamed:
							  [NSStringFromClass([self class]) stringByAppendingString:imageSuffix]]];
	}
}

- (void) _checkForEnabled {
	if (!_waitingForStartup) return;
	
	_timesCheckedForStartup++;
	
	if (_timesCheckedForStartup > 20) {
		_timesCheckedForStartup = 0;
		_waitingForStartup = NO;
	}
	
	[self updateEnabledUI];
	
	if (_waitingForStartup)
		[self performSelector:@selector(_checkForEnabled) withObject:nil afterDelay:1.0];
}

- (void) enableWithUserInteraction {
	[_spinner startAnimation:self];
	_waitingForStartup = YES;
	_timesCheckedForStartup = 0;
	[_enabledButton setEnabled:NO];
	
	NSError* err = nil;
	if (![self enableWithError:&err])
		[[self mainView] presentError:err modalForWindow:[[self mainView] window] delegate:self didPresentSelector:@selector(_enableDidResolveError:context:) contextInfo:NULL];
	
	[self _checkForEnabled];
}

- (void) _enableDidResolveError:(BOOL) resolved context:(void*) nothing {
	if (resolved) [self enableWithUserInteraction];
}

- (void) disableWithUserInteraction {
	[_spinner startAnimation:self];
	[_enabledButton setEnabled:NO];
	
	[self performSelector:@selector(updateEnabledUI) withObject:self afterDelay:1.0];
	
	NSError* err = nil;
	if (![self disableWithError:&err])
		[[self mainView] presentError:err modalForWindow:[[self mainView] window] delegate:self didPresentSelector:@selector(_disableDidResolveError:context:) contextInfo:NULL];
}

- (void) _disableDidResolveError:(BOOL) resolved context:(void*) nothing {
	if (resolved) [self disableWithUserInteraction];
}

- (BOOL) enableWithError:(NSError**) error {
	if (![self disableWithError:error])
		  return NO;
	
	NSString* agentsDir = [self agentsDirectoryWithError:error];
	if (!agentsDir) return NO;
	
	NSString* plistName = [kPlSAgentIdentifier stringByAppendingPathExtension:@"plist"];
	plistName = [agentsDir stringByAppendingPathComponent:plistName];
	
	NSFileManager* fm = [NSFileManager defaultManager];
	NSDictionary* dict;
	if ([fm fileExistsAtPath:plistName]) {
		dict = [NSMutableDictionary dictionaryWithContentsOfFile:plistName];
		[(NSMutableDictionary*)dict addEntriesFromDictionary:[self defaultLaunchdPropertyList]];
	} else
		dict = [self defaultLaunchdPropertyList];
	
	[dict writeToFile:plistName atomically:YES];
	
	NSTask* task = [NSTask new];
	[task setLaunchPath:@"/bin/launchctl"];
	[task setArguments:[NSArray arrayWithObjects:@"load", @"-wF", @"-S", @"Aqua", plistName, nil]];
	[task launch];
	[task waitUntilExit];
	
	if ([task terminationStatus] != 0) {
		if (error) *error = [self launchctlError];
		return NO;
	}
	
	return YES;
}

- (BOOL) disableWithError:(NSError**) error {
	NSString* agentsDir = [self agentsDirectoryWithError:error];
	if (!agentsDir) return NO;
	
	NSString* plistName = [kPlSAgentIdentifier stringByAppendingPathExtension:@"plist"];
	plistName = [agentsDir stringByAppendingPathComponent:plistName];
	
	NSFileManager* fm = [NSFileManager defaultManager];

	if ([fm fileExistsAtPath:plistName]) {
		NSTask* task = [NSTask new];
		[task setLaunchPath:@"/bin/launchctl"];
		[task setArguments:[NSArray arrayWithObjects:@"unload", @"-wF", @"-S", @"Aqua", plistName, nil]];
		[task launch];
		[task waitUntilExit];
		
		if ([task terminationStatus] != 0) {
			if (error) *error = [self launchctlError];
			return NO;
		}
		
		if (![fm removeItemAtPath:plistName error:error])
			return NO;
	}
	
	return YES;
}

- (NSString*) agentsDirectoryWithError:(NSError**) error {
	NSArray* ar = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
	if ([ar count] == 0) {
		if (error) *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:ENOENT userInfo:nil];
		return nil;
	}
	
	NSString* dir = [[ar objectAtIndex:0] stringByAppendingPathComponent:@"LaunchAgents"];
	NSFileManager* fm = [NSFileManager defaultManager];
	if (![fm fileExistsAtPath:dir] && ![fm createDirectoryAtPath:dir withIntermediateDirectories:YES attributes:nil error:error])
		return nil;
	
	return dir;
}
	

- (void) dealloc {
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	
	[_plugIns release];
	[_excludedApps release];
	[_managers release];
	[_prefs release];
	
	[super dealloc];
}

- (id) initWithBundle:(NSBundle*) b {
	if (self = [super initWithBundle:b]) {
		_plugIns = [NSMutableArray new];
		_excludedApps = [NSMutableArray new];
		
		_prefs = [PlSCFPreferences new];
		
		_managers = [[NSArray alloc] initWithObjects:
					 [[PlSInputManagerManager new] autorelease],
					 [[PlSSIMBLManager new] autorelease],
					 nil];
		
		for (PlSManager* mgr in _managers)
			mgr.preferencesController = _prefs;
		
		NSImage* img = [[NSImage alloc] initWithContentsOfFile:
						[[self bundle] pathForImageResource:@"PlusOn"]];
		[img setName:[NSStringFromClass([self class]) stringByAppendingString:@"_PlusOn"]];
		img = [[NSImage alloc] initWithContentsOfFile:
						[[self bundle] pathForImageResource:@"PlusOff"]];
		[img setName:[NSStringFromClass([self class]) stringByAppendingString:@"_PlusOff"]];
		
		_updateUI = [PlSUpdateUI new];
		[_updateUI setApplicationBundleAndLocale:b];
	}
	
	return self;
}

- (void) willSelect {
	NSURL* url = [NSURL fileURLWithPath:[[self bundle] bundlePath]];
	FSRef ref;
	if (CFURLGetFSRef((CFURLRef) url, &ref))
		AHRegisterHelpBook(&ref);
	
	@try {
		id <L0PlugSuitWire> proxy = (id <L0PlugSuitWire>) [NSConnection rootProxyForConnectionWithRegisteredName:kL0PlugSuitConnectionName host:nil];
		if (proxy) { [proxy shutDown]; _migratingFrom1_0 = YES; }
	} @catch (NSException* ex) {}
	
	[self updatePlugIns];
	[self updateExcludedApplications];
	[self updateEnabledUI];
	
	[[NSNotificationCenter defaultCenter]
	 addObserver:self selector:@selector(_appReactivated:) name:NSApplicationDidBecomeActiveNotification object:nil];
}

- (void) didSelect {
	[self updateHelpMenuWithArray:
	 [NSArray arrayWithObject:
	  [NSDictionary dictionaryWithObjectsAndKeys:
	   PlSLocalizedString(@"PlugSuit Help", @"Help menu item title") , @"title",
	   @"PlugSuitHelpContents", @"anchor",
	  nil]]];
	
	if (!self.isEnabled && _migratingFrom1_0)
		[self enableWithUserInteraction];
	
	if (!self.isEnabled && _prefs.shouldTryEnabling)
		[self enableWithUserInteraction];
	
	_prefs.shouldTryEnabling = NO;
	
	NSDate* date = [_prefs lastUpdateCheckDate];
	
	if (date && (-[date timeIntervalSinceNow]) > 604800) { // one week
		_updateUI.displayNoticeIfNoUpdate = NO;
		if (_prefs.checkForPlugSuitUpdates)
			[self beginUpdatingApplication];
		else if (_prefs.checkForPlugInUpdates)
			[self beginUpdatingPlugIns];
			
		
		[_prefs markNowAsLastUpdateCheckDate];
	} else if (!date)
		[_prefs markNowAsLastUpdateCheckDate];

	[_prefs notifyPreferencesChanged];
}
	
- (void) beginUpdatingPlugIns {
	NSEnumerator* enu = [[_plugIns objectEnumerator] retain];
	PlSPlugIn* pin = [enu nextObject];
	
	if (pin)
		[_updateUI beginCheckForUpdateForPlugIn:pin modalForWindow:[[self mainView] window] delegate:self didEndSelector:@selector(_updateUI:plugInUpdated:context:) context:(void*) enu];
}

- (void) beginUpdatingApplication {
	[_updateUI beginCheckForAppUpdateModalForWindow:[[self mainView] window] delegate:self didEndSelector:@selector(_updateUI:appUpdated:context:) context:nil];
}

- (void) _updateUI:(PlSUpdateUI*) ui appUpdated:(PlSUpdateResult) result context:(void*) nothing {
	
	if (_prefs.checkForPlugInUpdates)
		[self beginUpdatingPlugIns];
}

- (void) _updateUI:(PlSUpdateUI*) ui plugInUpdated:(PlSUpdateResult) result context:(void*) enu {
	
	PlSPlugIn* pin = [(NSEnumerator*)enu nextObject];
	if (pin)
		[_updateUI beginCheckForUpdateForPlugIn:pin modalForWindow:[[self mainView] window] delegate:self didEndSelector:@selector(_updateUI:plugInUpdated:context:) context:(void*) enu];
	else
		[(id)enu release];
}

- (void) willUnselect {
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	[_aboutPanel orderOut:self];
}

- (void) _appReactivated:(id) n {
	[self updatePlugIns];
	L0LogS(@"reactivated.");
}

- (void) updatePlugIns {
	for (PlSManager* mgr in _managers)
		[mgr rescan];
	
	[_plugIns removeAllObjects];
	
	[self willChangeValueForKey:@"plugIns"];
	for (PlSManager* mgr in _managers) {
		for (PlSPlugIn* pin in mgr.plugIns)
			[_plugIns addObject:pin];
	}
	[self didChangeValueForKey:@"plugIns"];
}

- (NSMutableArray*) plugIns {
	return _plugIns;
}

- (void) updateExcludedApplications {
	[self willChangeValueForKey:@"excludedApplications"];
	
	[_excludedApps removeAllObjects];
	for (NSString* path in [_prefs excludedApplicationPaths]) {
		L0Log(@"updating excluded app path = %@", path);
		NSBundle* bundle = [NSBundle bundleWithPath:path];
		if (bundle)
			[_excludedApps addObject:bundle];
	}
	
	[self didChangeValueForKey:@"excludedApplications"];
	
	L0Log(@"updated excluded apps = %@", _excludedApps);
}

- (NSMutableArray*) excludedApplications {
	return _excludedApps;
}

- (IBAction) enableDisable:(id) sender {
	L0Log(@"is enabled = %d", [self isEnabled]);
	if (![self isEnabled])
		[self enableWithUserInteraction];
	else
		[self disableWithUserInteraction];
}

- (IBAction) addPlugIn:(id) sender {
	NSOpenPanel* open = [NSOpenPanel openPanel];
	[open setCanChooseDirectories:YES];
	[open beginSheetForDirectory:nil file:nil modalForWindow:[[self mainView] window] modalDelegate:self didEndSelector:@selector(_addPlugInOpenPanel:didEndWithCode:context:) contextInfo:NULL];
}

- (void) _addPlugInOpenPanel:(NSOpenPanel*) panel didEndWithCode:(int) returnCode  context:(void*) nothing {
	if (returnCode == NSCancelButton) return;
	
	[panel orderOut:self];
	[self installPlugInAtPath:[panel filename]];
}

- (void) installPlugInAtPath:(NSString*) plugInPath {
	// check quarantine
	NSURL* fileURL = [NSURL fileURLWithPath:plugInPath];
	FSRef ref; NSDictionary* attrs;
	CFURLGetFSRef((CFURLRef) fileURL, &ref);
	OSStatus oserr = LSCopyItemAttribute(&ref, kLSRolesAll, kLSItemQuarantineProperties, (CFTypeRef*) &attrs);
	
	if (oserr == noErr) {
		[attrs autorelease];
		if ([[attrs objectForKey:(id) kLSQuarantineTypeKey] isEqual:(id) kLSQuarantineTypeWebDownload]) {
			NSString* agent = [attrs objectForKey:(id) kLSQuarantineAgentNameKey];
			NSURL* url = [attrs objectForKey:(id) kLSQuarantineOriginURLKey];
			NSDate* timestamp = [attrs objectForKey:(id) kLSQuarantineTimeStampKey];
			
			NSDateFormatter* date = [[NSDateFormatter alloc] init];
			[date setFormatterBehavior:NSDateFormatterBehavior10_4];
			[date setDateStyle:NSDateFormatterFullStyle];
			[date setTimeStyle:NSDateFormatterShortStyle];
			NSLocale* l = [[NSLocale alloc] initWithLocaleIdentifier:[[[self bundle] preferredLocalizations] objectAtIndex:0]];
			[date setLocale:l];
			[l release];			
			NSString* dateString = [date stringFromDate:timestamp];
			[date release];
			
			NSAlert* a = [NSAlert alertNamed:@"PlSWebQuarantine" inBundle:[self bundle]];
			[a setAlertStyle:NSCriticalAlertStyle];
			[a setMessageText:
			 [NSString stringWithFormat:[a messageText], [plugInPath lastPathComponent]]];
			[a setInformativeText:
			 [NSString stringWithFormat:[a informativeText], agent, [url host], dateString]];
			int result = [a runModal];
			
			switch (result) {
				NSAlertThirdButtonReturn: // open web page
					[[NSWorkspace sharedWorkspace] openURL:url];
				NSAlertSecondButtonReturn: // cancel (we also return for open web page)
					return;
			}
			
		} else {
			NSString* agent = [attrs objectForKey:(id) kLSQuarantineAgentNameKey];
			NSDate* timestamp = [attrs objectForKey:(id) kLSQuarantineTimeStampKey];
			
			NSDateFormatter* date = [[NSDateFormatter alloc] init];
			[date setFormatterBehavior:NSDateFormatterBehavior10_4];
			[date setDateStyle:NSDateFormatterFullStyle];
			[date setTimeStyle:NSDateFormatterShortStyle];
			NSLocale* l = [[NSLocale alloc] initWithLocaleIdentifier:[[[self bundle] preferredLocalizations] objectAtIndex:0]];
			[date setLocale:l];
			[l release];
			NSString* dateString = [date stringFromDate:timestamp];
			[date release];
			
			NSAlert* a = [NSAlert alertNamed:@"PlSGenericQuarantine" inBundle:[self bundle]];
			[a setAlertStyle:NSCriticalAlertStyle];
			[a setMessageText:
			 [NSString stringWithFormat:[a messageText], [plugInPath lastPathComponent]]];
			[a setInformativeText:
			 [NSString stringWithFormat:[a informativeText], agent, dateString]];
			
			if ([a runModal] == NSAlertSecondButtonReturn)
				return;
		}
		
		// clear quarantine
		LSSetItemAttribute(&ref, kLSRolesAll, kLSItemQuarantineProperties, NULL);
	}
	
	BOOL installedIt = NO;
	NSError* err;
	for (PlSManager* mgr in _managers) {
		BOOL doneTrying = NO;
		err = nil;
		
		while (!doneTrying) {
			PlSInstallStatus status = [mgr installPlugInAtPath:plugInPath andReturnError:&err];
			if (status == kPlSInstallDone) {
				installedIt = YES;
				break;
			} else if (status == kPlSInstallError) {
				if (![NSApp presentError:err])
					doneTrying = YES;
			} else if (status == kPlSInstallIncompatible) {
				doneTrying = YES;
			}
		}
	}
	
	if (!installedIt && err == nil) {
		NSAlert* alert = [NSAlert alertNamed:@"PlSNoSuitablePlugInManager" inBundle:[self bundle]];
		[alert setMessageText:
		 [NSString stringWithFormat:[alert messageText], [[NSFileManager defaultManager] displayNameAtPath:plugInPath]]];
		[alert beginSheetModalForWindow:[[self mainView] window] modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
	}
	
	[self updatePlugIns];	
}

- (IBAction) removePlugIn:(id) sender {
	if ([[_controller selectedObjects] count] == 0) { NSBeep(); return; }
	
	NSAlert* alert = [NSAlert alertNamed:@"PlSRemoval" inBundle:[self bundle]];
	[alert beginSheetModalForWindow:[[self mainView] window] modalDelegate:self didEndSelector:@selector(_removalAlert:didEndWithCode:context:) contextInfo:NULL];
}

- (void) _removalAlert:(NSAlert*) a didEndWithCode:(NSInteger) code context:(void*) nothing {
	if (code == NSAlertSecondButtonReturn)
		return;
	
	for (PlSPlugIn* pin in [_controller selectedObjects]) {
		NSInteger dummy;
		if (pin.canRemoveByDeletingUserVisiblePath)
			[[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation source:[pin.userVisiblePath stringByDeletingLastPathComponent] destination:@"" files:[NSArray arrayWithObject:[pin.userVisiblePath lastPathComponent]] tag:&dummy];
	}
	
	[[a window] orderOut:self];
	[self updatePlugIns];

	NSAlert* alert = [NSAlert alertNamed:@"PlSRemovalDone" inBundle:[self bundle]];
	[alert beginSheetModalForWindow:[[self mainView] window] modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
}

- (IBAction) addExcludedApplication:(id) sender {
	NSOpenPanel* open = [NSOpenPanel openPanel];
	[open beginSheetForDirectory:@"/Applications" file:nil modalForWindow:[[self mainView] window] modalDelegate:self didEndSelector:@selector(_excludedApplicationChoice:returned:context:) contextInfo:NULL];
}

- (void) _excludedApplicationChoice:(NSOpenPanel*) op returned:(NSInteger) ret context:(void*) nothing {
	if (ret == NSCancelButton) return;
	
	[self excludeApplicationAtPath:[op filename]];
}

- (void) excludeApplicationAtPath:(NSString*) path {
	if (![[path pathExtension] isEqual:@"app"]) { 
		NSBeep(); _appsView.draggingCanceled = YES;
		return;
	}
	
	NSBundle* bundle = [NSBundle bundleWithPath:path];
	if (!bundle) { 
		NSBeep(); _appsView.draggingCanceled = YES;
		return;
	}
	
	_appsView.draggingCanceled = ![_prefs setExcluded:YES forApplication:bundle];
	if (_appsView.draggingCanceled) NSBeep();
	
	[self updateExcludedApplications];
}

- (IBAction) removeExcludedApplication:(id) sender {
	NSArray* sel = [NSArray arrayWithArray:[_appsController selectedObjects]];
	for (NSBundle* bundle in sel)
		[_prefs setExcluded:NO forApplication:bundle];
	
	[self updateExcludedApplications];
}

- (IBAction) showPlugInInFinder:(id) sender {
	PlSPlugIn* sel = [[_controller selectedObjects] objectAtIndex:0];
	[[NSWorkspace sharedWorkspace] selectFile:sel.userVisiblePath inFileViewerRootedAtPath:@""];
}
- (IBAction) showExcludedApplicationInFinder:(id) sender {
	NSArray* sel = [_appsController selectedObjects];
	for (NSBundle* bundle in sel)
		[[NSWorkspace sharedWorkspace] selectFile:[bundle bundlePath] inFileViewerRootedAtPath:@""];
}

- (IBAction) openPlugInWebSite:(id) sender {
	for (PlSPlugIn* pin in [_controller selectedObjects]) {
		if (pin.webSiteURL)
			[[NSWorkspace sharedWorkspace] openURL:pin.webSiteURL];
	}
}
- (IBAction) checkForPlugInUpdates:(id) sender {
	_updateUI.displayNoticeIfNoUpdate = YES;
	PlSPlugIn* pin = [[_controller selectedObjects] objectAtIndex:0];
	
	[_updateUI beginCheckForUpdateForPlugIn:pin modalForWindow:[[self mainView] window] delegate:nil didEndSelector:NULL context:NULL];
}

- (IBAction) explainNotManaged:(id) sender {
	NSArray* sels = [_controller selectedObjects];

	if ([sels count] == 1 && [[sels objectAtIndex:0] isKindOfClass:[PlSSIMBLPlugIn class]]) {
		NSAlert* a = [NSAlert alertNamed:@"PlSExplainPlugInManagedBySIMBL" inBundle:[self bundle]];
		[a beginSheetModalForWindow:[[self mainView] window] modalDelegate:self didEndSelector:@selector(_explainPlugInManagedBySIMBLEnded:returned:context:) contextInfo:NULL];
	} else {
		NSAlert* a = [NSAlert alertNamed:@"PlSExplainPlugInNotManaged" inBundle:[self bundle]];
		[a beginSheetModalForWindow:[[self mainView] window] modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
	}
}

- (void) _explainPlugInManagedBySIMBLEnded:(NSAlert*) a returned:(NSInteger) ret context:(void*) nothing {
	if (ret == NSAlertSecondButtonReturn) {
		NSString* pathToSIMBL = [[[NSBundle bundleWithIdentifier:@"net.culater.SIMBL"] bundlePath] stringByDeletingLastPathComponent];
		
		if (pathToSIMBL) {
			if (![[NSWorkspace sharedWorkspace] selectFile:pathToSIMBL inFileViewerRootedAtPath:@""]) NSBeep();
		} else
			NSBeep();
	}
}

- (BOOL) validateMenuItem:(NSMenuItem*) item {
	L0Log(@"validating %@", [item title]);
	if ([item action] == @selector(openPlugInWebSite:))
		return [[_controller selectedObjects] count] == 1 &&
		[[[_controller selectedObjects] objectAtIndex:0] webSiteURL] != nil;
	else if ([item action] == @selector(checkForPlugInUpdates:))
		return [[_controller selectedObjects] count] == 1 &&
		[[[_controller selectedObjects] objectAtIndex:0] updatesCheckURL] != nil;
	else
		return YES;
}

- (IBAction) showAbout:(id) sender {
	if (![_aboutPanel isVisible])
		[_aboutPanel center];
	[_aboutPanel makeKeyAndOrderFront:self];
}

- (IBAction) showUpdatePreferences:(id) sender {
	[NSApp beginSheet:_updatesPanel modalForWindow:[[self mainView] window] modalDelegate:nil didEndSelector:NULL contextInfo:NULL];
}

- (IBAction) hideUpdatePreferences:(id) sender {
	[_prefs notifyPreferencesChanged];
	[NSApp endSheet:_updatesPanel];
	[_updatesPanel orderOut:self];
}

- (PlSCFPreferences*) preferences {
	return _prefs;
}

- (IBAction) showPrivacyPolicy:(id) sender {
	NSString* path = [[self bundle] pathForResource:@"Privacy Policy" ofType:@"pdf"];
	if (path) [[NSWorkspace sharedWorkspace] openFile:path];
	else NSBeep();
}

- (IBAction) checkNowForUpdates:(id) sender {
	_updatesFound = NO;
	_internetDown = NO;
	if (_prefs.checkForPlugSuitUpdates)
		[self beginUpdatingApplicationExplicit];
	else if (_prefs.checkForPlugInUpdates)
		[self beginUpdatingPlugInsExplicit];
}

- (void) beginUpdatingPlugInsExplicit {
	NSEnumerator* enu = [[_plugIns objectEnumerator] retain];
	PlSPlugIn* pin = [enu nextObject];
	
	if (pin)
		[_updateUI beginCheckForUpdateForPlugIn:pin modalForWindow:[[self mainView] window] delegate:self didEndSelector:@selector(_updateUI:plugInUpdatedExplicit:context:) context:(void*) enu];
}

- (void) beginUpdatingApplicationExplicit {
	[_updateUI beginCheckForAppUpdateModalForWindow:nil delegate:self didEndSelector:@selector(_updateUI:appUpdatedExplicit:context:) context:nil];
}

- (void) _updateUI:(PlSUpdateUI*) ui appUpdatedExplicit:(PlSUpdateResult) result context:(void*) nothing {
	switch (result) {
		case kPlSUpdateResultCancel:
		case kPlSUpdateResultAccept:
			_updatesFound = YES;
			break;
			
		case kPlSUpdateResultNotAvailable:
			_internetDown = YES;
			break;
	}
	
	if (_prefs.checkForPlugInUpdates)
		[self beginUpdatingPlugInsExplicit];
	else
		[self performSelector:@selector(wrapUpExplicit) withObject:nil afterDelay:0.001];
}

- (void) _updateUI:(PlSUpdateUI*) ui plugInUpdatedExplicit:(PlSUpdateResult) result context:(void*) enu {
	switch (result) {
		case kPlSUpdateResultCancel:
		case kPlSUpdateResultAccept:
			_updatesFound = YES;
			break;
	}
	
	PlSPlugIn* pin = [(NSEnumerator*)enu nextObject];
	if (pin)
		[_updateUI beginCheckForUpdateForPlugIn:pin modalForWindow:nil delegate:self didEndSelector:@selector(_updateUI:plugInUpdatedExplicit:context:) context:(void*) enu];
	else {
		[(id)enu release];
		[self performSelector:@selector(wrapUpExplicit) withObject:nil afterDelay:0.001];
	}
}

- (void) wrapUpExplicit {
	if (!_updatesFound) {
		NSAlert* a;
		if (!_internetDown)
			a = [NSAlert alertNamed:@"PlSNoUpdateAvailable" inBundle:[self bundle]];
		else
			a = [NSAlert alertNamed:@"PlSInternetDown" inBundle:[self bundle]];
		
		[a runModal];
	}
}

- (IBAction) showHelp:(id) sender {
	[[NSHelpManager sharedHelpManager] openHelpAnchor:@"PlugSuitHelpContents" inBook:@"PlugSuit Help"];
}

@end
